/*
 * Copyright 2002-2005 Robert Breidecker.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package net.sourceforge.jsorter;

import java.util.Comparator;
import java.util.List;

/**
 * The default comparator for the Sort and SwingSorter classes. This comparator
 * is used for sorting multiple columns.
 * 
 * @author Robert Breidecker
 */
public class SortComparator implements Comparator {
	/**
	 * The list of sort columns.
	 */
	private List sortColumns = null;

	/**
	 * Indicate how to treat null data values.
	 */
	private int nullBehavior = SorterConstants.NULLS_ARE_INVALID;

	/**
	 * Comparator constructor.
	 * 
	 * @param sortColumns
	 *            A list of SortColumns that represent the positions of columns
	 *            in your data that you want to be evaluated in the sort. The
	 *            sort will start with the first column position in the list.
	 *            Column positions not in the list will not be used in the sort.
	 *            The number of items in this list should equal the number of
	 *            items in the columnOrders list.
	 * 
	 * @param nullBehavior
	 *            An integer representing the constant that indicates how null
	 *            data values should behave while being sorted. See the null
	 *            behavior constants in this class.
	 */
	public SortComparator(final List sortColumns, final int nullBehavior) {
		this.sortColumns = sortColumns;
		this.nullBehavior = nullBehavior;
	}

	/**
	 * Overrides the java.util.Comparator compare method. This method is used
	 * for comparing two dimensional data. See the standard JDK documention for
	 * more information.
	 * 
	 * @param one
	 *            Object - The first object used in the compare. This field
	 *            should be a list of lists containing objects which implement
	 *            the Comparable interface. Some of these object types include
	 *            String, Integer, Long, Short, Float, Byte, Double and Date.
	 *            See the standard JDK documention for Comparator for a complete
	 *            list. The object type for each column of data must be
	 *            consistent or a ClassCaseException will be thrown.
	 * 
	 * @param two
	 *            Object - The second object used in the compare. This field
	 *            should be a list of lists containing objects which implement
	 *            the Comparable interface. Some of these object types include
	 *            String, Integer, Long, Short, Float, Byte, Double and Date.
	 *            See the standard JDK documention for Comparator for a complete
	 *            list. The object type for each column of data must be
	 *            consistent or a ClassCaseException will be thrown.
	 * 
	 * @return A negative integer, zero, or a positive integer as the first
	 *         argument is less than, equal to, or greater than the second.
	 * 
	 * @exception ClassCastException
	 *                Data in a column in not all of the same data type.
	 */
	public int compare(final Object one, final Object two) throws ClassCastException {
		// The number of columns in the table.
		int numColumns;

		// The return value.
		int rtn = 0;

		// Used for counting the number of real fields in the data.
		int ctr = 0;

		// Holds the type of sort order being used.
		int columnOrder;

		// Used for counting the number of values in the sort columns.
		int compareCtr;

		// One row of data;
		final List listOne = (List)one;

		if (sortColumns == null) {
			numColumns = listOne.size();
		} else {
			numColumns = sortColumns.size();
		}

		while (rtn == 0 && ctr < numColumns) {
			// The first object to compare.
			Comparable comparableOne;

			// The second object to compare.
			Comparable comparableTwo;

			// Make sure compare column is within range.
			if (sortColumns == null) {
				compareCtr = ctr;
			} else {
				compareCtr = ((SortableColumn)sortColumns.get(ctr)).getColumnPosition();
			}

			if (compareCtr <= listOne.size()) {
				// Another row of data;
				final List listTwo = (List) two;

				// Get the field to use in the compare.
				if (sortColumns == null) {
					comparableOne = (Comparable)listOne.get(compareCtr);
					comparableTwo = (Comparable)listTwo.get(compareCtr);
				} else {
					comparableOne = (Comparable)listOne.get(((SortableColumn) sortColumns.get(ctr)).getColumnPosition());
					comparableTwo = (Comparable)listTwo.get(((SortableColumn) sortColumns.get(ctr)).getColumnPosition());
				}

				// Get the sort type that goes with the sort column.
				if (sortColumns == null) {
					// If no sort columns were specified, then use ascending
					// order.
					columnOrder = SorterConstants.ASCENDING_ORDER;
				} else {
					columnOrder = ((SortableColumn)sortColumns.get(ctr)).getColumnOrder();
				}

				// Compare the objects.
				if (comparableOne != null && comparableTwo != null) {
					if (columnOrder == SorterConstants.ASCENDING_ORDER) {
						try {
							rtn = comparableOne.compareTo(comparableTwo);
						} catch (ClassCastException exception) {
							throw exception;
						}
					} else {
						try {
							rtn = comparableTwo.compareTo(comparableOne);
						} catch (ClassCastException exception) {
							throw exception;
						}
					}
				} else {
					if (nullBehavior == SorterConstants.NULLS_ARE_INVALID) {
						throw new IllegalStateException("Null data values are not valid.");
					} else if (comparableOne == null && comparableTwo != null) {
						if (columnOrder == SorterConstants.ASCENDING_ORDER) {
							rtn = -1;
						} else {
							rtn = 1;
						}

						if (nullBehavior == SorterConstants.NULLS_ARE_GREATEST) {
							rtn = rtn * -1;
						}
					} else if (comparableOne != null && comparableTwo == null) {
						if (columnOrder == SorterConstants.ASCENDING_ORDER) {
							rtn = 1;
						} else {
							rtn = -1;
						}

						if (nullBehavior == SorterConstants.NULLS_ARE_GREATEST) {
							rtn = rtn * -1;
						}
					} else {
						rtn = 0;
					}
				}
			}
			ctr++;
		}
		return rtn;
	}

	/**
	 * Returns the null behavior for this object.
	 * 
	 * @return An integer representing the constant that indicates how null data
	 *         values should behave while being sorted. See the null behavior
	 *         constants in this class. The default value for this class is
	 *         NULLS_ARE_INVALID.
	 */
	public int getNullBehavior() {
		return nullBehavior;
	}

	/**
	 * Set the null behavior for this object.
	 * 
	 * @param nullBehavior
	 *            An integer representing the constant that indicates how null
	 *            data values should behave while being sorted. See the null
	 *            behavior constants in this class.
	 * 
	 * @throws IllegalArgumentException
	 *             Thrown if the null behavior value is not valid.
	 */
	public void setNullBehavior(final int nullBehavior) {
		if (nullBehavior != SorterConstants.NULLS_ARE_GREATEST
				&& nullBehavior != SorterConstants.NULLS_ARE_INVALID
				&& nullBehavior != SorterConstants.NULLS_ARE_LEAST) {
			throw new IllegalArgumentException("Invalid null behavior.");
		}

		this.nullBehavior = nullBehavior;
	}
}